﻿
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Petaframe.Caching
{

    public delegate TResult Func<T1, T2, TResult>(T1 t1, T2 t2);
    public delegate TResult Func<TResult>();
    public delegate void Action();

    public sealed class Cache<TKey, TItem> : ICacheJob, ICache<TKey, TItem> where TKey : IEquatable<TKey>
    {
        private const int ReaderLockTime = 120000;
        private const int WriteLockTime = 10000;
        class CacheItem
        {
            public TKey Key { get; set; }
            public TItem Item { get; set; }
            public BaseToken Token { get; set; }
        }

        private List<CacheItem> mItemList = new List<CacheItem>();

        private ReaderWriterLock mRWLock = new ReaderWriterLock();

        private Action mOnReset;
        public Action OnReset
        {
            get { return mOnReset; }
            set { mOnReset = value; }
        }

        internal Cache()
        {
        }

        public void Add(TKey key, TItem item, BaseToken token)
        {
            Add(new Cache<TKey, TItem>.CacheItem
            {
                Key = key,
                Item = item,
                Token = token
            });
        }

        public void Add(TKey key, TItem item)
        {
            Add(new Cache<TKey, TItem>.CacheItem
            {
                Key = key,
                Item = item,
                Token = new BaseToken()
            });
        }

        private void Add(CacheItem cacheItem)
        {
            mRWLock.AcquireWriterLock(WriteLockTime);
            try
            {
                var item = mItemList.Find(n => n.Key.Equals(cacheItem.Key));
                if (item != null)
                {
                    mItemList.Remove(item);
                    cacheItem.Token.ResetFilter |= item.Token.ResetFilter;
                    mItemList.Add(cacheItem);
                }
                else
                    mItemList.Add(cacheItem);
            }
            finally
            {
                mRWLock.ReleaseWriterLock();
            }
        }

        public void AddIfNotExists(TKey key, Func<TItem> getvalues, BaseToken token)
        {
            mRWLock.AcquireWriterLock(WriteLockTime);
            try
            {
                var item = mItemList.Find(n => n.Key.Equals(key));
                if (item == null)
                {
                    mItemList.Add(new Cache<TKey, TItem>.CacheItem
                    {
                        Key = key,
                        Item = getvalues(),
                        Token = token
                    });
                }
            }
            finally
            {
                mRWLock.ReleaseWriterLock();
            }
        }

        public void AddRange(IEnumerable<KeyValuePair<TKey, TItem>> items, BaseToken token)
        {
            if (items == null)
                return;

            mRWLock.AcquireWriterLock(WriteLockTime);
            try
            {

                List<KeyValuePair<TKey, TItem>> newItemList = new List<KeyValuePair<TKey, TItem>>(items);

                foreach (var newItem in newItemList)
                {
                    var item = mItemList.Find(n => n.Key.Equals(newItem.Key));
                    if (item != null)
                    {
                        mItemList.Remove(item);
                        token.ResetFilter |= item.Token.ResetFilter;
                    }
                    mItemList.Add(new Cache<TKey, TItem>.CacheItem
                    {
                        Key = newItem.Key,
                        Item = newItem.Value,
                        Token = token
                    });
                }
            }
            finally
            {
                mRWLock.ReleaseWriterLock();
            }
        }

        public void AddRange(Func<IEnumerable<KeyValuePair<TKey, TItem>>> getRange, BaseToken token)
        {
            mRWLock.AcquireWriterLock(WriteLockTime);

            try
            {

                var items = getRange();
                if (items == null)
                    return;

                List<KeyValuePair<TKey, TItem>> newItemList = new List<KeyValuePair<TKey, TItem>>(items);

                foreach (var newItem in newItemList)
                {
                    var item = mItemList.Find(n => n.Key.Equals(newItem.Key));
                    if (item != null)
                    {
                        mItemList.Remove(item);
                        token.ResetFilter |= item.Token.ResetFilter;
                    }
                    mItemList.Add(new Cache<TKey, TItem>.CacheItem
                    {
                        Key = newItem.Key,
                        Item = newItem.Value,
                        Token = token
                    });
                }
            }
            finally
            {
                mRWLock.ReleaseWriterLock();
            }
        }
        public void Remove(TKey key)
        {
            mRWLock.AcquireReaderLock(ReaderLockTime);
            try
            {
                var item = mItemList.Find(n => n.Key.Equals(key));
                if (item != null)
                {
                    var wLock = mRWLock.UpgradeToWriterLock(WriteLockTime);
                    mItemList.Remove(item);
                    mRWLock.DowngradeFromWriterLock(ref wLock);
                }
            }
            finally
            {
                mRWLock.ReleaseReaderLock();
            }
        }

        public bool TryGet(TKey key, out TItem outItem)
        {
            mRWLock.AcquireReaderLock(ReaderLockTime);
            try
            {
                var item = mItemList.Find(n => n.Key.Equals(key));
                if (item == null)
                {
                    outItem = default(TItem);
                    return false;
                }
                else
                    outItem = item.Item;

                item.Token.Renew();
                return true;
            }
            finally
            {
                mRWLock.ReleaseReaderLock();
            }
        }

        public bool ContainsKey(TKey key)
        {
            TItem item;
            return TryGet(key, out item);
        }

        public TItem Get(TKey key)
        {
            TItem item;
            TryGet(key, out item);
            return item;
        }

        public TItem GetWithCreate(TKey key, Func<TItem> createItem, BaseToken token)
        {
            mRWLock.AcquireReaderLock(ReaderLockTime);
            try
            {
                var item = mItemList.Find(n => n.Key.Equals(key));
                if (item == null)
                {
                    var rLock = mRWLock.UpgradeToWriterLock(WriteLockTime);

                    try
                    {
                        item = mItemList.Find(n => n.Key.Equals(key));
                        if (item != null)
                            return item.Item;

                        item = new Cache<TKey, TItem>.CacheItem
                        {
                            Key = key,
                            Item = createItem(),
                            Token = token
                        };

                        Add(item);
                    }
                    finally
                    {
                        mRWLock.DowngradeFromWriterLock(ref rLock);
                    }
                }
                else
                    item.Token.Renew();

                return item.Item;
            }
            finally
            {
                mRWLock.ReleaseReaderLock();
            }
        }

        public long Count()
        {
            mRWLock.AcquireReaderLock(ReaderLockTime);
            try
            {
                return mItemList.Count;
            }
            finally
            {
                mRWLock.ReleaseReaderLock();
            }
        }

        public void BeginAddRange()
        {
            mRWLock.AcquireWriterLock(WriteLockTime);
        }

        public void EndAddRange()
        {
            mRWLock.ReleaseWriterLock();
        }

        #region ICacheJob Members

        public void Reset()
        {
            ((ICacheJob)this).Reset(0);
        }

        

        #endregion

        #region IDisposable Members

        #endregion


        void ICacheJob.Reset(long resetFilter)
        {
            mRWLock.AcquireWriterLock(WriteLockTime);
            try
            {
                if (resetFilter == 0)
                {
                    if (typeof(TItem) is IDisposable)
                    {
                        mItemList.ForEach(n => ((IDisposable)n).Dispose());
                    }
                    mItemList.Clear();
                }
                else
                {
                    var resetItems = mItemList.FindAll(n => (n.Token.ResetFilter & resetFilter) != 0);
                    if (typeof(TItem) is IDisposable)
                    {
                        resetItems.ForEach(n => ((IDisposable)n).Dispose());
                    }
                    resetItems.ForEach(n => mItemList.Remove(n));
                }

                if (mOnReset != null)
                    mOnReset();
            }
            finally
            {
                mRWLock.ReleaseWriterLock();
            }
        }


        #region ICacheJob Members


        void ICacheJob.CheckExpiration()
        {
            mRWLock.AcquireReaderLock(ReaderLockTime);
            try
            {
                for (int i = 0; i < mItemList.Count; i++)
                {
                    var current = mItemList[i];
                    if (!current.Token.IsCurrent)
                    {
                        var wLock = mRWLock.UpgradeToWriterLock(WriteLockTime);

                        try
                        {
                            if (!mItemList.Contains(current))
                                continue;
                            else
                            {
                                mItemList.RemoveAt(i);
                                i--;
                                continue;
                            }
                        }
                        finally
                        {
                            mRWLock.DowngradeFromWriterLock(ref wLock);
                        }
                    }
                }
            }
            finally
            {
                mRWLock.ReleaseReaderLock();
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            ((ICacheJob)this).Reset(0);
        }

        #endregion
    }

}
